/********************************************************************
 * This file includes functions to check the  annotation on
 * the physical pb_graph_node and pb_graph_pin
 *******************************************************************/
/* Headers from vtrutil library */
#include "check_pb_graph_annotation.h"

#include "pb_type_utils.h"
#include "vtr_assert.h"
#include "vtr_log.h"
#include "vtr_time.h"

/* begin namespace openfpga */
namespace openfpga {

/********************************************************************
 * A function to check if a physical pb_graph_pin for a pb_graph_pin
 * Print error to log file/screen when checking fails
 *******************************************************************/
static bool check_physical_pb_graph_pin(
  t_pb_graph_pin* pb_graph_pin,
  const VprDeviceAnnotation& vpr_device_annotation) {
  if (nullptr == vpr_device_annotation.physical_pb_graph_pin(pb_graph_pin)) {
    VTR_LOG_ERROR(
      "Found a pb_graph_pin '%s' missing physical pb_graph_pin binding!\n",
      pb_graph_pin->port->name);
    return false;
  }

  return true;
}

/********************************************************************
 * This function will recursively walk through all the pb_graph nodes
 * starting from a top node.
 * It aims to check
 * - if each primitive pb_graph node has been binded to a physical
 *   pb_graph_node
 * - if each pin of the primitive pb_graph node has been binded to
 *   a physical graph_pin
 *******************************************************************/
static void rec_check_vpr_physical_pb_graph_node_annotation(
  t_pb_graph_node* pb_graph_node,
  const VprDeviceAnnotation& vpr_device_annotation, size_t& num_err) {
  /* Go recursive first until we touch the primitive node */
  if (false == is_primitive_pb_type(pb_graph_node->pb_type)) {
    for (int imode = 0; imode < pb_graph_node->pb_type->num_modes; ++imode) {
      for (int ipb = 0;
           ipb < pb_graph_node->pb_type->modes[imode].num_pb_type_children;
           ++ipb) {
        /* Each child may exist multiple times in the hierarchy*/
        for (int jpb = 0;
             jpb <
             pb_graph_node->pb_type->modes[imode].pb_type_children[ipb].num_pb;
             ++jpb) {
          rec_check_vpr_physical_pb_graph_node_annotation(
            &(pb_graph_node->child_pb_graph_nodes[imode][ipb][jpb]),
            vpr_device_annotation, num_err);
        }
      }
    }
    return;
  }

  /* Ensure that the pb_graph_node has been mapped to a physical node */
  t_pb_graph_node* physical_pb_graph_node =
    vpr_device_annotation.physical_pb_graph_node(pb_graph_node);
  if (nullptr == physical_pb_graph_node) {
    VTR_LOG_ERROR(
      "Found a pb_graph_node '%s' missing physical pb_graph_node binding!\n",
      pb_graph_node->pb_type->name);
    num_err++;
    return; /* Invalid pointer already, further check is not applicable */
  }

  /* Reach here, we should have a valid pointer to the physical pb_graph_node,
   * Check the physical pb_graph_pin for each pin
   */
  for (int iport = 0; iport < physical_pb_graph_node->num_input_ports;
       ++iport) {
    for (int ipin = 0; ipin < physical_pb_graph_node->num_input_pins[iport];
         ++ipin) {
      if (false == check_physical_pb_graph_pin(
                     &(physical_pb_graph_node->input_pins[iport][ipin]),
                     vpr_device_annotation)) {
        num_err++;
      }
    }
  }

  for (int iport = 0; iport < physical_pb_graph_node->num_output_ports;
       ++iport) {
    for (int ipin = 0; ipin < physical_pb_graph_node->num_output_pins[iport];
         ++ipin) {
      if (false == check_physical_pb_graph_pin(
                     &(physical_pb_graph_node->output_pins[iport][ipin]),
                     vpr_device_annotation)) {
        num_err++;
      }
    }
  }

  for (int iport = 0; iport < physical_pb_graph_node->num_clock_ports;
       ++iport) {
    for (int ipin = 0; ipin < physical_pb_graph_node->num_clock_pins[iport];
         ++ipin) {
      if (false == check_physical_pb_graph_pin(
                     &(physical_pb_graph_node->clock_pins[iport][ipin]),
                     vpr_device_annotation)) {
        num_err++;
      }
    }
  }
}

/********************************************************************
 * Check each primitive pb_graph_node
 * - It has been binded to an physical pb_graph_node
 * - Each pin has been binded to a physical pb_graph_node pin
 *******************************************************************/
void check_physical_pb_graph_node_annotation(
  const DeviceContext& vpr_device_ctx,
  const VprDeviceAnnotation& vpr_device_annotation) {
  size_t num_err = 0;

  for (const t_logical_block_type& lb_type :
       vpr_device_ctx.logical_block_types) {
    /* By pass nullptr for pb_graph head */
    if (nullptr == lb_type.pb_graph_head) {
      continue;
    }
    rec_check_vpr_physical_pb_graph_node_annotation(
      lb_type.pb_graph_head, vpr_device_annotation, num_err);
  }

  if (0 == num_err) {
    VTR_LOG("Check pb_graph annotation for physical nodes and pins passed.\n");
  } else {
    VTR_LOG_ERROR(
      "Check pb_graph annotation for physical nodes and pins failed with %ld "
      "errors!\n",
      num_err);
  }
}

} /* end namespace openfpga */
